﻿module LexCast

open System

let private (|WithCtorFromString|_|) (tp : Type) =
    let ctor = tp.GetConstructor( [| typeof<string> |] )
    if ctor <> null then Some ctor else None

let private (|WithStaticPropertyNamed|_|) (name : string) (tp : Type) =
    let prop = tp.GetProperty( name, Reflection.BindingFlags.Static )
    if prop <> null then Some prop else None

let private TYPE_TO_CONVERTER =
    let makeConverter (cfun : string -> 'T) =
        typeof<'T>, fun s -> cfun(s) :> obj
    dict [
        makeConverter Convert.ToBoolean
        makeConverter Convert.ToByte
        makeConverter Convert.ToSByte
        makeConverter Convert.ToInt16
        makeConverter Convert.ToUInt16
        makeConverter Convert.ToInt32
        makeConverter Convert.ToUInt32
        makeConverter Convert.ToInt64
        makeConverter Convert.ToUInt64
        makeConverter Convert.ToChar
        makeConverter Convert.ToDouble
        makeConverter Convert.ToSingle
    ]

let private (|WithConverter|_|) (tp : Type) =
    let pp, cvfun = TYPE_TO_CONVERTER.TryGetValue( tp )
    if pp then Some cvfun else None

let rec objFromString (t : Type) (s : string) =
    match t with
    | t when t = typeof<string> -> 
        s :> obj
    | t when t.IsEnum -> 
        System.Enum.Parse( t, s, true )
    | WithConverter cvfun -> 
        cvfun s 
    | WithCtorFromString ctor -> 
        ctor.Invoke( [| s :> obj |] )
    | WithStaticPropertyNamed s prop -> 
        prop.GetGetMethod().Invoke( null, [| |] )
    | t -> 
        failwithf "No way to obtain '%s' from string" t.Name

let lexCast<'T> (s : string) =
    objFromString typeof<'T> s :?> 'T
